<?php


/** This libary contains XML (parsing) related clases and functions.
 *  License?
 *
 *  @author Dante Lorenso & Jyry Kuukkanen
 *
 *  $Id: soxml.php,v 1.2 2005/03/12 17:59:29 jyry Exp $
 */


/** Basic node element used by Xml2Array
 *  @var string $Name Node name
 *  @var any $Data Node data
 *  @var array $Attrs Possible attributes for this node.
 *  @var array $Elems Possible sub-nodes, elements, for this node.
 */
class soXmlNode {
    var $Name = '';
    var $Data = '';
    var $Attrs = array();
    var $Elems = array();
}; // soXmlNode


/** Class to convert a XML feed to nested array.
 *  Example: $xml2a = new Xml2Array();
 *           $xml2a->parse($xml_text);
 *           echo $xml2a->dump();
 *           $array = $xml2a->getRoot(); // root array
 *  Each Element and sub-element in the array is an XmlNode object, and each
 *  node may, or may not, contain sub-modes in it's $Elems property.
 *
 *  @author Dante Lorenso & Jyry Kuukkanen
 *
 *  TODO:
 *  - Search functionality
 *  - XmlNode should carry "Level" property to indicate the depth of the 
 */
class soXml2Array {

    //----------------------------------------------------------------------
    // private variables
    var $parser;
    var $node_stack = array();
    var $__Root = NULL;

    //----------------------------------------------------------------------
    /** PUBLIC
     * If a string is passed in, parse it right away.
     */
    function soXml2Array($xmlstring="") {
        if ($xmlstring) return($this->parse($xmlstring));
    } // soXml2Array

    //----------------------------------------------------------------------
    /** PUBLIC
     *  Parse a text string containing valid XML into a multidimensional array
     *  located at rootnode.
     */
    function parse($xmlstring="") {
        // set up a new XML parser to do all the work for us
        $this->parser = xml_parser_create();
        xml_set_object($this->parser, $this);
        xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, false);
        xml_set_element_handler($this->parser, "startElement", "endElement");
        xml_set_character_data_handler($this->parser, "characterData");

        // Build a Root node and initialize the node_stack...
        $this->node_stack = array();
        $this->startElement(null, "root", array());

        // parse the data and free the parser...
        xml_parse($this->parser, $xmlstring);
        xml_parser_free($this->parser);

        // recover the root node from the node stack
        $rnode = array_pop($this->node_stack);

        // return the root node...
        $this->__Root = $rnode;
    }

    //----------------------------------------------------------------------
    /** PROTECTED
     * Start a new Element.  This means we push the new element onto the stack
     * and reset it's properties.
     */
    function startElement($parser, $Name, $Attrs) {
        // create a new node...
        $node = new soXmlNode();
        $node->Name = $Name;
        foreach ($Attrs as $key => $value) {
            $node->Attrs[$key] = $value;
        }

        // add the new node to the end of the node stack
        array_push($this->node_stack, $node);
    }

    //----------------------------------------------------------------------
    /** PROTECTED
     * End an element.  This is done by popping the last element from the
     * stack and adding it to the previous element on the stack.
     */
    function endElement($parser, $name) {
        // pop this element off the node stack
        $node = array_pop($this->node_stack);
        $node->Data = trim($node->Data);

        // and add it an an element of the last node in the stack...
        $lastnode = count($this->node_stack);
        array_push($this->node_stack[$lastnode-1]->Elems, $node);
    }

    //----------------------------------------------------------------------
    /** PROTECTED
     * Collect the data onto the end of the current chars.
     */
    function characterData($parser, $Data) {
        // add this data to the last node in the stack...
        $lastnode = count($this->node_stack);
        $this->node_stack[$lastnode-1]->Data .= $Data;
    }

    
    /** Returns the root array.
     *  @return array Root array.
     */
    function getRoot() {
        return $this->__Root;
    } // getRoot
    
    
    /** Returns the dump of the array.
     *  @return string Array dump.
     */
    function dump() {
        return $this->__dump($this->__Root->Elems);
    } // dump


    /** Actual dump function that does something
     *  @param int Recursion level -- for internal usage only.
     *  @return string Array dump.
     */
    function __dump(&$Elements, $Level = 0) {
        $result = '';
        foreach ($Elements as $elem) {
            for ($i = 0; $i < $Level; $i++) {
                $result .= $i."\t";
            }; // for

            $result .= $elem->Name.' == "'.$elem->Data.'"'."\n";
            if ($elem->Elems) {
                $result .= $this->__dump($elem->Elems, $Level +1);
            }; // if
        }; // foreach

        return $result;
    } // __dump


}; // soXml2Array



/*

# TEST BLOCK

$xml_text = file_get_contents('../datafeed_example.xml');
$xml2a  = new soXml2Array();
$xml2a->parse($xml_text);
echo $xml2a->dump();
die("\n");
$root_node  = $xml2a->getRoot();
$drive      = array_shift($root_node["_ELEMENTS"]);

#var_dump($drive);

# END of test block
*/

?>
